home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Workbench Add-On
/
Workbench Add-On - Volume 1.iso
/
Dev
/
Amiga-E
/
E_v3.2a_extras
/
PdSrc
/
Lang
/
NGRC.e
Wrap
Text File
|
1992-09-02
|
22KB
|
743 lines
/* Noise Compiler v1.0
Short: The noise compiler is a program that translates source files in
a type of "music programming language" into Noise/ProTracker (tm)
compatible files.
The noise compiler reads ascii source files containing a description
of a musical piece, and then starts generating a sequence of notes by
recursively walking down your definition. finally, it will load all
samples and write all data to a ProTracker (tm) file.
A noise grammar program (extension ".ngr") consist of one to unlimited
number of so called "rules" (it really helps if you already know something
about context free grammars). Each rule looks like:
<sym> "->" <symlist> ";"
example:
beat -> drum [C#,1] /* a C-flat */
{ [D,2] | [D#,2] | [Db,2] } /* D, D-sharp, D-flat */
( boomboom 1 ); /* index=1 */
every token may be separated by a whitespace, which denotes any number of
spaces/tabs/linefeeds, and comments which start with "/*" and end
with "*/", and may be nested infinitely.
a <symlist> is one or more <item>s. an <item> can be:
<sym> <index>
will be recursively replaced by the definition of <sym>.
<index> is optional and explained later
example: drum
"(" <weight> <symlist> ")"
as <sym>, only an optional <weight> (default=500, range 0-1000) specifies
the chance of <symlist> getting rewritten, with 0=no chance at all.
example:
(beat) (250 beat)
decide while generating if the defintion for "beat" gets played or
not, resp. 50% and 25% chance.
"{" <weight> <symlist> "|" ..... "}"
any number of <symlist>s may be between the {}, separated by a bar |.
<weight> is again optional, and again between 0-1000, only now
the default =1. With no weights, simply one of the <simlist>s is
picked. examples:
{ beat | boomboom | [Gb+,4] } /* chances: each 33% */
{ 3 beat | 7 boomboom } /* chances: 30%,70% resp. */
<samplename> <vol>
an AmigaDos path to a sample, <vol> is an optional integer, range 0-64
example: "samples:guitar.iff"
"[" <note> "," <duration> "]"
a note specification, see below.
<hexinteger>
a special effects spec., see below.
Notes.
a <note> is a character like: C D E F G A B
it may be followed by any number of modifiers, where:
"#" = sharp
"b" = flat
"+" = octave up
"-" = octave down
in general, you can get only one octave up/down, and the parser will
give an error if you write something like "E+++"
Any number of "#" and "b" are allowed, only in general you would
write "D" for "C##", or "F" for "E#" etc. Note that the current
version of the noise compiler only allows for C-major. Examples:
C C (the one in the middle of a piano)
C#+ C-sharp one octave up
Dbb- C one octave down
for those who are not at all familiar with classical notes, a note
may also be specified as an integer ranging from -12 to 23, with
0 being the middle C again, and negative numbers lower notes.
the <duration> is an integer specifying the time in units spent
on playing the note, before starting the new one, range = 1-100
A unit is about a quarter of a normal note, so a whole note should
be written "4" etc. examples:
[C,1]
[F#,4] /* an F-sharp for one second */
Sound Effects.
various souneffects may be used as an hexadecimal integer.
Such an effect only works with the next following note.
Example:
mainpart -> $E01 drums solo drums
puts hardware audio filter off before play. See protracker documentation
for a summary of effects.
Indexing.
while () and {|||} are nice ways to either generate totally random music, or
just make your pieces sound more natural due to some variation, one often
wishes to have the random choices made repeated: for example, if
you design a symbol "beat" that you wish to re-use in certain parts of
your program, and the definition contains some random-variation, you
may want to have the random choices fixed for the second time you use it,
because it would sound totally random otherwise. for example, considering
the pseudo-definition of "beat" above:
beats -> beat boomboom beat boomboom;
this looks like a normal rhytm, but "beat" is replaced by two different
sequences of notes. with:
beats -> beat1 boomboom2 beat1 boomboom2;
you specify that with the first "beat", the random choices are recorded
and all others that are similar indexed will have their notes generated
according to the first. note that this doesn't work for samples, i.e:
beats -> "bass.iff" beat1 "hihat.iff" beat1;
will generate two exactly the same sequences, only played by different
instruments.
Symbols and Channels.
A symbol consists of any number of lowercase characters. As the
Amiga plays four channels simulanously, there's not one start
symbol for the grammar, but four, called: "one", "two", "three",
"four". Atleast one of these has to be defined in a grammar.
Example:
one -> "dat:noiz/Dguitar" 20 aa1;
two -> "dat:noiz/drumz/bassdrum" aa1;
aa -> a $E00 a $E01 a a;
a -> { c d c c d d | d d | c d c d | c c };
c -> [C,1] [C#,3];
d -> [D,2] [D,3];
plays the sequence "aa" simultanously over two channels, by two
different instruments, with volumes 20 and 64. the sequence "aa"
consists of several instances of "a", while switching filter
on/off. "a" consist of 4 possible sequences, which in turn
consist of notes to be played. (NOTE: don't try these examples,
they no pieces of real music, just "examples")
Designing Tunes.
For example, for a small guitar tune, we would start with
a defintion of some notes, a and b
a -> [D,1] [E,3];
b -> [D,1] [D#,7];
Then, we would combine them into a sequence:
c -> a a a b;
Finally, we play those over channel one, and define a sample:
one -> guitar c c c a [C,16]; /* just one channel */
/* our set of instruments */
guitar -> "dat:noiz/Dguitar";
note that notes get played by a certain instrument from the point it
is encountered in the grammar.
Using Randomness.
You may use randomness to bring subtle variations into your music,
for example, with:
mynote -> { 10 [D,2] | 1 [D#,2] | 1 [Db,2] };
we define a note that is played like a "D" most of the time, but
occasionally flat or sharp. We can easily do the same thing with
the duration, or even with the order a certain sequence gets played.
*/
OBJECT sym /* primairy structure of rewrite symbols */
next,type,name,rptr
ENDOBJECT
OBJECT rlist /* linked list structure for grammar */
next,type,index,info
ENDOBJECT
OBJECT optset /* structure for storing { | | } exp. */
next,rptr,weight
ENDOBJECT
OBJECT sample /* all data about a given sample */
path,len,adr,vol
ENDOBJECT
OBJECT i /* indexing of rewritten trees */
start,len,isym
ENDOBJECT
ENUM SYM,OPTSET,OPTION,NOTE,SAMPLE,SFX /* rlist.type */
ENUM NOTYPE,REWRITE /* sym.type */
ENUM NOMEM,NOFILE,NOFORM,NOGRAM,STACKFLOW, /* errors */
BADSTRUCTURE,BREAK,WRITEMOD,READSAMPLE
CONST MAXINDEX=1000,MAXROWS=64*4*64,MAXDURATION=100
CONST MAXDATA=MAXROWS*4,MAXSAMPLE=31,MAXNOTE=23,MINNOTE=-12
CONST PARSE_ER=100,GEN_ER=200,MASK=$0FFF0FFF
RAISE NOMEM IF New()=NIL, /* define exceptions */
NOMEM IF String()=NIL,
STACKFLOW IF FreeStack()<1000,
BREAK IF CtrlC()=TRUE
DEF buf,flen,p,tokeninfo,symlist=NIL:PTR TO sym,ltoken=-1,numsample=0,
notes,np:PTR TO LONG,maxrows=0,cursample=0,cursfx=0,curglob=0,end,
timings:PTR TO INT,fh=NIL,notevals:PTR TO LONG
DEF sdata[32]:ARRAY OF sample,
itab[MAXINDEX]:ARRAY OF i,
channel[4]:ARRAY OF i,
infile[100]:STRING,outfile[100]:STRING
PROC main() HANDLE
WriteF('Noise Compiler v1.0\n')
WriteF('Translates NoiseGrammar programs into ProTracker modules!\n')
readgrammar()
WriteF('grammar "\s" loaded. Parsing...\n',infile)
parsegrammar()
WriteF('Grammar parsed succesfully. Generating...\n')
generate()
WriteF('Noise generated. Now loading samples...\n')
loadsamples()
WriteF('Now saving to file "\s".\n',outfile)
writemodule()
WriteF('done.\n')
EXCEPT
IF fh THEN Close(fh) /* lowest level exception handler: */
WriteF('Terminating: ') /* general error report */
SELECT exception
CASE NOFILE; WriteF('Could not load "\s" grammar file!\n',infile)
CASE NOMEM; WriteF('Not enough memory!\n')
CASE NOFORM; WriteF('Grammar format error!\n')
CASE STACKFLOW; WriteF('Stack overflow! (too heavy recursion?)\n')
CASE BADSTRUCTURE; WriteF('Problems while generating.\n')
CASE NOGRAM; WriteF('No rules rewritten!\n')
CASE BREAK; WriteF('Stopped by user\n')
CASE WRITEMOD; WriteF('Unable to write PT module "\s"!\n',outfile)
CASE READSAMPLE; WriteF('Unable to read sample(s)!\n')
ENDSELECT
DeleteFile(outfile)
ENDPROC
PROC readgrammar()
StrCopy(infile,arg,ALL)
StrAdd(infile,'.ngr',ALL) /* '#?.ngr' = NoizGRammar */
StrCopy(outfile,arg,ALL) /* '#?.mod' = ProTracker format */
StrAdd(outfile,'.mod',ALL)
IF (flen:=FileLength(infile))<1 THEN Raise(NOFILE)
IF (fh:=Open(infile,OLDFILE))=NIL THEN Raise(NOFILE)
IF Read(fh,buf:=New(flen+1),flen)<>flen THEN Raise(NOFILE)
Close(fh)
fh:=NIL
buf[flen]:=";" /* for parser */
ENDPROC
/* this is the parser part. we use a simple but powerfull top-down
parser, and build our syntax tree here. */
ENUM ER_UNTOKEN=PARSE_ER,ER_UNEXPECTED,ER_QUOTE,ER_SYMEXP,ER_DOUBLE,
ER_ARROWEXP,ER_RPARENTHEXP,ER_RBRACEEXP,ER_EMPTY,ER_EOLEXP,ER_RANGE,
ER_COMMENT,ER_UNDEF,ER_RBRACKETEXP,ER_MAXSAMPLE,ER_NOSAMPLE,
ER_INTEGEREXP,ER_COMMAEXP,ER_NOTEEXP
ENUM EOF,EOL,ARROW,BAR,COMMA, /* ; -> | , */
RSYM,INTEGER,HEXINTEGER, /* sym 100 $E01 */
ISTRING,NOTEVAL, /* "" C#+ */
LBRACE,RBRACE,LPARENTH, /* { } ( */
RPARENTH,LBRACKET,RBRACKET /* ) [ ] */
PROC parsegrammar() HANDLE
DEF end,spot,sl:PTR TO sym,s,i
notevals:=[9,11,0,2,4,5,7]
p:=buf
WHILE parserule() DO NOP
p:=NIL
IF (sl:=symlist)=NIL THEN Raise(NOGRAM)
IF numsample=0 THEN Raise(ER_NOSAMPLE)
REPEAT
IF sl.type=NOTYPE /* check for undefined symbols */
s:=sl.name
Raise(ER_UNDEF)
ENDIF
UNTIL (sl:=sl.next)=NIL
EXCEPT /* re-throw if unknown exception */
IF exception>=PARSE_ER THEN WriteF('ERROR: ') ELSE Raise(exception)
WriteF(ListItem(['Unexpected lexical item\n',
'Unexpected characters in line!\n',
'Unmatched quote "\n',
'Symbol expected\n',
'Double definition of symbol\n', /* language errors */
'"->" expected\n',
'")" expected\n',
'"}" expected\n',
'Empty rewrite-list\n',
'End of rule expected\n',
'Integer/Note value out of range\n',
'Incorrectly nested comment(s)\n',
'No rule defined for symbol "\s"\n',
'"]" expected\n',
'Maximum of 32 samples exceeded\n',
'Grammar needs atleast one sample\n',
'Integer expected\n',
'"," expected\n',
'Note expected'],exception-PARSE_ER),s)
IF p /* display very nice error indication */
IF p[-1]=";" THEN DEC p
spot:=p
WHILE (p[]--<>";") AND (p[]<>10) AND (p<>buf) DO NOP
INC p
spot:=spot-p+5
end:=p
WHILE (end[]<>";") AND (end[]++<>10) DO NOP
end[]--:=0
WriteF('LINE: \s\n',p)
FOR i:=1 TO spot DO WriteF(' ')
WriteF('^\n')
ENDIF
Raise(NOFORM)
ENDPROC
PROC parserule()
DEF token,csym:PTR TO sym
IF (token:=gettoken())=EOF
RETURN FALSE
ELSEIF token=RSYM
csym:=tokeninfo
IF csym.type<>NOTYPE THEN Raise(ER_DOUBLE)
IF gettoken()<>ARROW THEN Raise(ER_ARROWEXP)
csym.rptr:=parseitemlist()
csym.type:=REWRITE
IF gettoken()<>EOL THEN Raise(ER_EOLEXP)
ELSE
Raise(ER_SYMEXP)
ENDIF
ENDPROC TRUE
PROC parseitemlist()
DEF item:PTR TO rlist,prev:PTR TO rlist,ilist=NIL
prev:={ilist}
WHILE (item:=parseitem())<>NIL
prev.next:=item
prev:=item
ENDWHILE
IF ilist=NIL THEN Raise(ER_EMPTY)
ENDPROC ilist
PROC parseitem()
DEF token,item:PTR TO rlist,t2,prev:PTR TO optset,
curr:PTR TO optset,olist,totalw=0
token:=gettoken()
IF token=RSYM
item:=New(SIZEOF rlist)
item.type:=SYM
item.info:=tokeninfo
IF (t2:=gettoken())=INTEGER
item.index:=checkinfo(1,MAXINDEX-1)
ELSE
putback(t2)
item.index:=0
ENDIF
ELSEIF token=ISTRING
item:=New(SIZEOF rlist)
item.type:=SAMPLE
sdata[numsample].path:=tokeninfo
IF (t2:=gettoken())=INTEGER
sdata[numsample].vol:=checkinfo(0,64)
ELSE
putback(t2)
sdata[numsample].vol:=64
ENDIF
item.info:=numsample++
IF numsample=MAXSAMPLE THEN Raise(ER_MAXSAMPLE)
ELSEIF token=LBRACE /* parse { | | ... } */
item:=New(SIZEOF rlist)
item.type:=OPTSET
prev:={olist}
REPEAT
curr:=New(SIZEOF optset)
IF (token:=gettoken())=INTEGER /* check for weight */
curr.weight:=checkinfo(0,1000)
ELSE
curr.weight:=1
putback(token)
ENDIF
totalw:=totalw+curr.weight
curr.rptr:=parseitemlist()
prev.next:=curr
prev:=curr
UNTIL (token:=gettoken())<>BAR
IF token<>RBRACE THEN Raise(ER_RBRACEEXP)
item.info:=olist
item.index:=totalw /* we store weight here */
ELSEIF token=LPARENTH
item:=New(SIZEOF rlist) /* parse ( ) */
item.type:=OPTION
IF (token:=gettoken())=INTEGER /* check for weight */
item.index:=checkinfo(0,1000)
ELSE
item.index:=500
putback(token)
ENDIF
item.info:=parseitemlist()
IF gettoken()<>RPARENTH THEN Raise(ER_RPARENTHEXP)
ELSEIF token=LBRACKET
item:=New(SIZEOF rlist) /* parse [note,duration] */
item.type:=NOTE
token:=gettoken()
IF (token<>INTEGER) AND (token<>NOTEVAL) THEN Raise(ER_NOTEEXP)
item.info:=checkinfo(MINNOTE,MAXNOTE)
IF gettoken()<>COMMA THEN Raise(ER_COMMAEXP)
IF gettoken()<>INTEGER THEN Raise(ER_INTEGEREXP)
item.index:=checkinfo(1,MAXDURATION)
IF gettoken()<>RBRACKET THEN Raise(ER_RBRACKETEXP)
ELSEIF token=HEXINTEGER
item:=New(SIZEOF rlist) /* parse $SFX */
item.type:=SFX
item.info:=checkinfo(0,$FFF)
ELSEIF (token=EOL) OR (token=RBRACE) OR (token=RPARENTH) OR (token=BAR)
putback(token)
RETURN NIL
ELSE
Raise(ER_UNTOKEN)
ENDIF
ENDPROC item
/* the lexical analyser: called by the parser each time it
needs a token. attribute values are in "tokeninfo". allows
for one symbol lookahead, with putback() function */
PROC gettoken()
DEF c,x,start,len,syml:PTR TO sym,s,depth
FreeStack(); CtrlC()
IF ltoken<>-1
x:=ltoken
ltoken:=-1
RETURN x
ENDIF
tokeninfo:=0
parse:
c:=p[]++
SELECT c
CASE ";"; RETURN IF buf+flen<p THEN p-- BUT EOF ELSE EOL
CASE "|"; RETURN BAR
CASE ","; RETURN COMMA
CASE "("; RETURN LPARENTH
CASE ")"; RETURN RPARENTH
CASE "{"; RETURN LBRACE
CASE "}"; RETURN RBRACE
CASE "["; RETURN LBRACKET
CASE "]"; RETURN RBRACKET
CASE "-"; IF p[]=">" THEN RETURN p++ BUT ARROW
CASE "/"
IF p[]="*"
x:=p
depth:=1
WHILE buf+flen>p++
IF (p[0]="/") AND (p[1]="*")
INC depth
INC p
ENDIF
IF (p[0]="*") AND (p[1]="/")
DEC depth
INC p
ENDIF
IF depth=0
INC p
BRA parse
ENDIF
ENDWHILE
p:=x
Raise(ER_COMMENT)
ENDIF
Raise(ER_UNEXPECTED)
CASE 34
start:=p
WHILE (p[]<>";") AND (p[]<>10) AND (p[]++<>34) DO NOP
IF p[-1]=";" THEN p-- BUT Raise(ER_QUOTE)
len:=p-start-1
tokeninfo:=String(len)
StrCopy(tokeninfo,start,len)
RETURN ISTRING
DEFAULT
IF (c>="a") AND (c<="z")
start:=p--
WHILE (p[]>="a") AND (p[]++<="z") DO NOP
len:=p---start
s:=String(len)
StrCopy(s,start,len)
syml:=symlist
WHILE syml
IF StrCmp(s,syml.name,ALL) THEN BRA found
syml:=syml.next
ENDWHILE
syml:=New(SIZEOF sym)
syml.next:=symlist
syml.name:=s
syml.type:=NOTYPE
symlist:=tokeninfo:=syml
RETURN RSYM
found:
tokeninfo:=syml
RETURN RSYM
ELSEIF (c>="A") AND (c<="G")
tokeninfo:=notevals[c-"A"]
LOOP
x:=p[]++
SELECT x
CASE "+"; tokeninfo:=tokeninfo+12 /* octave up */
CASE "-"; tokeninfo:=tokeninfo-12 /* octave down */
CASE "#"; tokeninfo:=tokeninfo+1 /* sharp */
CASE "b"; tokeninfo:=tokeninfo-1 /* flat */
DEFAULT
DEC p
RETURN NOTEVAL
ENDSELECT
ENDLOOP
ELSEIF ((c>="0") AND (c<="9")) OR (c="-") OR (c="$")
tokeninfo,x:=Val(p--)
p:=p+x
RETURN IF c="$" THEN HEXINTEGER ELSE INTEGER
ENDIF
IF c>32 THEN Raise(ER_UNEXPECTED) ELSE BRA parse
ENDSELECT
ENDPROC
PROC putback(token)
ltoken:=token
ENDPROC
PROC checkinfo(min,max) RETURN IF (tokeninfo<min) OR (tokeninfo>max) THEN
Raise(ER_RANGE) ELSE tokeninfo
ENUM NOCHANNEL=GEN_ER,LARGESONG,CROSSINDEX
PROC generate() HANDLE
DEF x,ci:PTR TO i,syms:PTR TO LONG,numc=0
Rnd(-Shl(VbeamPos(),14)) /* initialise seed */
ci:=itab
FOR x:=0 TO MAXINDEX-1 DO ci[].start++:=NIL
ci:=channel
timings:=[856,808,762,720,678,640,604,570,538,508,480,453,
428,404,381,360,339,320,302,285,269,254,240,226,
214,202,190,180,170,160,151,143,135,127,120,113]:INT
/* C- C#- D- D#- E- F- F#- G- G#- A- A#- B-
C C# D D# E F F# G G# A A# B
C+ C#+ D+ D#+ E+ F+ F#+ G+ G#+ A+ A#+ B+ */
WriteF('s\d\n',MAXDURATION*4+100+MAXDATA)
np:=notes:=New(MAXDURATION*4+100+MAXDATA)
end:=np+MAXDATA
syms:=['one','two','three','four']
FOR x:=0 TO 3
ci[x].start:=np
IF findsym(syms[x])
ci[x].len:=np-ci[x].start
IF ci[x].len>maxrows THEN maxrows:=ci[x].len
INC numc
ELSE
ci[x].start:=NIL
ENDIF
ENDFOR
IF numc=0 THEN Raise(NOCHANNEL)
IF maxrows=0 THEN Raise(NOGRAM)
IF maxrows>MAXROWS THEN Raise(LARGESONG)
EXCEPT
IF exception>=GEN_ER THEN WriteF('ERROR: ')
SELECT exception
CASE NOCHANNEL; WriteF('Atleast one channel must be defined\n')
CASE LARGESONG; WriteF('Song too large!\n')
CASE CROSSINDEX; WriteF('No cross-symbol indexing allowed\n')
DEFAULT; Raise(exception) /* re-throw if unknown */
ENDSELECT
Raise(BADSTRUCTURE) /* terminate */
ENDPROC
PROC findsym(name)
DEF s:PTR TO sym
s:=symlist
WHILE s
IF StrCmp(s.name,name,ALL) THEN BRA.S continue
s:=s.next
ENDWHILE
RETURN FALSE
continue:
rewritelist(s.rptr)
ENDPROC TRUE
PROC rewritelist(list:PTR TO rlist)
WHILE list
rewritesym(list)
list:=list.next
ENDWHILE
ENDPROC
PROC rewritesym(rsym:PTR TO rlist)
DEF t,sl:PTR TO sym,rnd,c1,c2,ol:PTR TO optset,x,i,st:PTR TO LONG,l,n
FreeStack(); CtrlC()
t:=rsym.type
SELECT t
CASE SYM
sl:=rsym.info
IF i:=rsym.index
st:=itab[i].start
l:=itab[i].len
IF st
IF np+l>=end THEN Raise(LARGESONG)
IF sl<>itab[i].isym THEN Raise(CROSSINDEX)
l:=Shr(l,2)
IF l THEN FOR x:=1 TO l DO np[]++:=IF n:=st[]++ THEN
n AND MASK OR curglob ELSE 0
ELSE
st:=np
rewritelist(sl.rptr)
itab[i].len:=np-st
itab[i].start:=st
itab[i].isym:=sl
ENDIF
ELSE
rewritelist(sl.rptr)
ENDIF
CASE OPTION
IF Rnd(1001)<rsym.index THEN rewritelist(rsym.info)
CASE OPTSET
rnd:=Rnd(rsym.index)
c1:=c2:=0
ol:=rsym.info
WHILE ol
c2:=c1+ol.weight
IF (rnd>=c1) AND (rnd<c2) THEN rewritelist(ol.rptr)
c1:=c2
ol:=ol.next
ENDWHILE
CASE NOTE
np[]++:=cursfx OR curglob OR Shl(timings[rsym.info+-MINNOTE],16)
IF rsym.index>1 THEN FOR x:=2 TO rsym.index DO np[]++:=0
IF np>=end THEN Raise(LARGESONG)
cursfx:=0
CASE SAMPLE
cursample:=rsym.info
curglob:=Shl(cursample+1 AND $F,12) OR Shl(cursample+1 AND $F0,24)
CASE SFX
cursfx:=rsym.info
ENDSELECT
ENDPROC
PROC loadsamples() HANDLE
DEF s:PTR TO sample,i,l,r,f:PTR TO LONG
s:=sdata
FOR i:=1 TO numsample
IF (l:=FileLength(s.path))<10 THEN Raise(0)
s.len:=l
s.adr:=New(l)
IF (fh:=Open(s.path,OLDFILE))=NIL THEN Raise(0)
r:=Read(fh,s.adr,l)
Close(fh)
fh:=NIL
IF r<10 THEN Raise(0)
f:=s.adr
IF f[]="FORM"
WHILE f[]++<>"BODY" DO IF s.adr+l<f THEN Raise(0)
s.len:=l+s.adr-f
s.adr:=f
ENDIF
s++
ENDFOR
EXCEPT
WriteF('While processing sample "\s":\n',s.path)
Raise(READSAMPLE)
ENDPROC
PROC writemodule()
DEF s,x,pnum,dat[4]:ARRAY OF LONG,nument,n,ch:PTR TO LONG,len,wl
IF (fh:=Open(outfile,NEWFILE))=NIL THEN Raise(WRITEMOD)
Write(fh,StringF(s:=String(19),'\l\s[20]',arg) BUT s,20)
FOR x:=0 TO MAXSAMPLE-1
wl:=Shr(sdata[x].len,1)
IF x>=numsample
Write(fh,[0,0,0,0,0,0,0,0],30)
ELSE
Write(fh,sdata[x].path,21)
Out(fh,0)
Write(fh,[wl,sdata[x].vol,0,1]:INT,8) /* or [,,wl,] */
ENDIF
ENDFOR
IF (pnum:=maxrows/256)*256<>maxrows THEN INC pnum
Out(fh,pnum)
Out(fh,120) /* 127 */
FOR x:=0 TO pnum-1 DO Out(fh,x)
FOR x:=pnum TO 127 DO Out(fh,0)
Write(fh,["M.K."],4)
nument:=pnum*64-1
FOR x:=0 TO nument
FOR n:=0 TO 3
ch:=channel[n].start
IF ch
len:=channel[n].len
IF len
dat[n]:=ch[]++
channel[n].start:=ch
channel[n].len:=len-4
ELSE
dat[n]:=0
ENDIF
ELSE
dat[n]:=0
ENDIF
ENDFOR
Write(fh,dat,16)
ENDFOR
FOR x:=0 TO numsample-1
Write(fh,sdata[x].adr,sdata[x].len)
ENDFOR
Close(fh)
fh:=NIL
ENDPROC